
package com.google.bitcoin.core;

import java.util.HashMap;
import java.util.HashMap;
import java.util.Collection;
import java.util.Map;

import com.google.bitcoin.store.BlockStore;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;


public class BlockChainCache implements java.io.Serializable
{
    public static final long serialVersionUID = 3L;

    private static final Logger log = LoggerFactory.getLogger(BlockChainCache.class);

    public static final File PATH=new File("data/blockchaincache.obj");


    private transient BlockChain chain;
    private transient BlockStore store;

    private HashMap<Sha256Hash, Integer> hash_to_height;
    private HashMap<Integer, Sha256Hash> height_to_hash;
    private Sha256Hash last_head;
    private int max_height;

    public BlockChainCache(BlockChain chain, BlockStore store)
    {
        this.chain=chain;
        this.store=store;
        hash_to_height=new HashMap<Sha256Hash, Integer>(1024,0.5f);
        height_to_hash=new HashMap<Integer, Sha256Hash>(1024,0.5f);
    }

    public void setBlockChain(BlockChain chain){this.chain = chain;}
    public void setBlockStore(BlockStore store){this.store = store;}

    public static BlockChainCache load(BlockChain chain, BlockStore store)
    {
        try
        {
            ObjectInputStream in = new ObjectInputStream(new FileInputStream(PATH));
            BlockChainCache c = (BlockChainCache) in.readObject();
            in.close();
            c.setBlockChain(chain);
            c.setBlockStore(store);
            return c;
        }
        catch(Throwable t)
        {   
            log.warn("Exception loading old cache", t);
            return new BlockChainCache(chain, store);
        }

    }

    public synchronized void save()
    {
        try
        {
            File dir = PATH.getParentFile();
            File tmp = File.createTempFile("blockcache",".tmp", dir);
            ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(tmp));
            out.writeObject(this);
            out.flush();
            out.close();
            tmp.renameTo(PATH);
            log.info("Save done");
        }
        catch(Throwable t)
        {
            log.warn("Unable to save cache", t);
        }
    }

    public synchronized void updateChain()
    {
        if (chain==null) return;
        if (store==null) return;

        if ((last_head!=null) && (chain.getChainHead().getHeader().getHash().equals(last_head)))
        {
            return;
        }

        StoredBlock block = chain.getChainHead();
        last_head=block.getHeader().getHash();
        max_height=block.getHeight();
        int found=0;
       
        while(block != null)
        {
            int height=block.getHeight();
            if (height < 160000) break;

            Sha256Hash hash = block.getHeader().getHash();

            if (height_to_hash.get(height) !=null)
            if (height_to_hash.get(height).equals(hash))
            {
                break;
            }
            if (height % 250==0) log.info("Height " + height);
            Sha256Hash old = height_to_hash.put(height, hash);
            if (old != null)
            {
                hash_to_height.remove(old);
            }
            hash_to_height.put(hash, height);
            found++;

            try
            {
                block = block.getPrev(store);
            }
            catch(com.google.bitcoin.store.BlockStoreException e)
            {
                throw new RuntimeException(e);
            }
        }
        log.info("Found " + found + " new blocks.  New highest: " + last_head + " (" + max_height + ")");
        save();


    }

    public synchronized int getDepth(Collection<Sha256Hash> blocks)
    {
        updateChain();


        int max=0;
        if (blocks==null) return max;
        for(Sha256Hash b : blocks)
        {
            Integer height = hash_to_height.get(b);
            if (height != null)
            {
                int depth=max_height - height + 1;

                max=Math.max(max, depth);
            }
        }
        return max;
    }

    public synchronized int getHeight(Collection<Sha256Hash> blocks)
    {
        updateChain();


        int min=Integer.MAX_VALUE;
        if (blocks==null) return min;
        for(Sha256Hash b : blocks)
        {
            Integer height = hash_to_height.get(b);
            if (height != null)
            {
                min=Math.min(height, min);

            }
        }
        return min;
    }


}
